package org.tuestudent.grouppa;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.util.EncodingUtils;

import android.util.Xml.Encoding;

public class MCrypt {

    private String iv = "groua08paSfvxcv3";
    private IvParameterSpec ivspec;
    private SecretKeySpec keyspec;
    private Cipher cipher;
    private static Charset charsetCoding = Charset.forName("UTF-8");
    private String SecretKey = "v0a8erce4SGzxn3c";

    public MCrypt() {
        ivspec = new IvParameterSpec(iv.getBytes());

        keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");

        try {
            cipher = Cipher.getInstance("AES/CBC/NoPadding");
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Encrypting to string Important: encodes from UTF-8
     * 
     * @param text
     *            text to be encoded
     * @return string with hex representation of encoded bytes
     * @throws Exception
     */
    public String encrypt(String text) throws Exception {
        return bytesToHex(encryptToBytes(text));
    }

    /**
     * Encrypting text to bytes Important: encodes from UTF-8
     * 
     * @param text
     *            text to be encrypted
     * @return bytes with encrypted text
     * @throws Exception
     */
    public byte[] encryptToBytes(String text) throws Exception {
        return encryptToBytes(text.getBytes(charsetCoding));
    }

    /**
     * Encrypting text(in bytes) to bytes Important: encodes from UTF-8
     * 
     * @param text
     *            text to be encrypted
     * @return bytes with encrypted text
     * @throws Exception
     */
    public byte[] encryptToBytes(byte[] text) throws Exception {
        if (text == null || text.length == 0)
            return new byte[0];

        // adding 4 bytes in the beginning with length of text
        byte[] lenText = new byte[text.length + 4];
        System.arraycopy(text, 0, lenText, 4, text.length);
        ByteBuffer.wrap(lenText, 0, 4).putInt(text.length);
        byte[] encrypted = null;

        try {
            cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);

            encrypted = cipher.doFinal(padString(lenText));
        } catch (Exception e) {
            throw new Exception("[encrypt] " + e.getMessage());
        }
        return encrypted;
    }

    /**
     * Decrypting from string (hexed representation of encrypted string)
     * Important: decodes to UTF-8
     * 
     * @param code
     *            encrypted string to decrypt
     * @return decrypted string
     * @throws Exception
     */
    public String decrypt(String code) throws Exception {
        if (code == null || code.length() == 0)
            return "";

        return decrypt(hexToBytes(code));
    }

    /**
     * Decrypting from byte array of encrypted string Important: decodes to
     * UTF-8
     * 
     * @param code
     *            encoded string to decrypt
     * @return decoded string
     * @throws Exception
     */
    public String decrypt(byte[] code) throws Exception {

        return new String(decryptToBytes(code), charsetCoding).trim();
    }

    /**
     * Decrypting from byte array of encrypted string
     * 
     * @param code
     *            encoded string to decrypt
     * @return decoded string in bytes
     * @throws Exception
     */
    public byte[] decryptToBytes(byte[] code) throws Exception {

        if (code == null || code.length == 0)
            return new byte[0];

        byte[] decrypted = null;

        try {
            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

            decrypted = cipher.doFinal(code);
        } catch (Exception e) {
            throw new Exception("[decrypt] " + e.getMessage());
        }
        int length = ByteBuffer.wrap(decrypted, 0, 4).getInt();

        if (length > decrypted.length || length < decrypted.length - 20)
            throw new Exception(
                    "Mcrypt.decryptToBytes: message length not good");
        byte[] decryptedPure = new byte[length];
        System.arraycopy(decrypted, 4, decryptedPure, 0, length);

        return decryptedPure;
    }

    public static String bytesToHex(byte[] data) {
        if (data == null) {
            return null;
        }

        int len = data.length;
        String str = "";
        for (int i = 0; i < len; i++ ) {
            if ((data[i] & 0xFF) < 16)
                str = str + "0" + java.lang.Integer.toHexString(data[i] & 0xFF);
            else
                str = str + java.lang.Integer.toHexString(data[i] & 0xFF);
        }
        return str;
    }

    public static byte[] hexToBytes(String str) {
        if (str == null) {
            return null;
        } else if (str.length() < 2) {
            return null;
        } else {
            int len = str.length() / 2;
            byte[] buffer = new byte[len];
            for (int i = 0; i < len; i++ ) {
                String number = str.substring(i * 2, i * 2 + 2);
                buffer[i] = (byte) Integer.parseInt(number, 16);

            }
            return buffer;
        }
    }

    private static byte[] padString(String source) {

        return padString(source.getBytes(charsetCoding));
    }

    private static byte[] padString(byte[] source) {

        char paddingChar = ' ';
        int size = 16;
        int x = source.length % size;
        if (x == 0)
            return source;
        int padLength = size - x;

        byte[] result = Arrays.copyOf(source, source.length + padLength);

        Arrays.fill(result, source.length, result.length, (byte) paddingChar);

        return result;
    }
}
